/*
 * Purpose: Virtual mixing audio driver recording routines
 */
#define COPYING Copyright (C) Hannu Savolainen and Dev Mazumdar 2006. All rights reserved.

#define SWAP_SUPPORT
#include <oss_config.h>
#include "vmix.h"

#if 0
/* TODO: Disable this debugging stuff */
extern unsigned char tmp_status;
# define UP_STATUS(v) OUTB(NULL, (tmp_status=tmp_status|(v)), 0x378)
# define DOWN_STATUS(v) OUTB(NULL, (tmp_status=tmp_status&~(v)), 0x378)
#else
# define UP_STATUS(v)
# define DOWN_STATUS(v)
#endif

#undef SINE_DEBUG

#ifndef VMIX_USE_FLOAT
#undef SINE_DEBUG
#endif

#ifdef SINE_DEBUG
#define SINE_SIZE	48
static const float sine_table[SINE_SIZE] = {
  0.000000, 0.130526, 0.258819, 0.382683,
  0.500000, 0.608761, 0.707107, 0.793353,
  0.866025, 0.923880, 0.965926, 0.991445,
  1.000000, 0.991445, 0.965926, 0.923880,
  0.866025, 0.793353, 0.707107, 0.608761,
  0.500000, 0.382683, 0.258819, 0.130526,
  0.000000, -0.130526, -0.258819, -0.382683,
  -0.500000, -0.608761, -0.707107, -0.793353,
  -0.866025, -0.923880, -0.965926, -0.991445,
  -1.000000, -0.991445, -0.965926, -0.923880,
  -0.866025, -0.793353, -0.707107, -0.608761,
  -0.500000, -0.382683, -0.258819, -0.130526
};
static int sine_phase[2] = { 0 };
#endif

/*
 * Recording import functions (from the physical devices)
 */
#undef  INT_IMPORT
#define INT_IMPORT(x) (x * 256)

static void
import16ne (vmix_engine_t * eng, void *inbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  short *op;
#define SAMPLE_TYPE	short
#define SAMPLE_RANGE	32768.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x

#include "vmix_import.inc"
}

static void
import16oe (vmix_engine_t * eng, void *inbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  short *op;
#undef  SAMPLE_TYPE
#undef  SAMPLE_RANGE
#define SAMPLE_TYPE	short
#define SAMPLE_RANGE	32768.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap16(x)

#include "vmix_import.inc"
}

#undef  INT_IMPORT
#define INT_IMPORT(x) (x / 256)

static void
import32ne (vmix_engine_t * eng, void *inbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  int *op;
#undef  SAMPLE_TYPE
#undef  SAMPLE_RANGE
#define SAMPLE_TYPE	int
#define SAMPLE_RANGE	2147483648.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x

#include "vmix_import.inc"
}

static void
import32oe (vmix_engine_t * eng, void *inbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  int *op;
#define SAMPLE_TYPE	int
#define SAMPLE_RANGE	2147483648.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap32(x)

#include "vmix_import.inc"
}

/*
 * recording export functions to virtual devices
 */
#undef  BUFFER_TYPE
#define BUFFER_TYPE short *

#undef  INT_EXPORT
#define INT_EXPORT(x) (x / 256)

void
vmix_rec_export_16ne (vmix_portc_t * portc, int nsamples)
{
  short *outp;
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#ifdef VMIX_USE_FLOAT
  double range = 32767.0;
#endif
#include "rec_export.inc"
}

void
vmix_rec_export_16oe (vmix_portc_t * portc, int nsamples)
{
  short *outp;
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap16(x)
#ifdef VMIX_USE_FLOAT
  double range = 32767.0;
#endif
#include "rec_export.inc"
}

#undef BUFFER_TYPE
#define BUFFER_TYPE int *
#undef  INT_EXPORT
#define INT_EXPORT(x) (x * 256)

void
vmix_rec_export_32ne (vmix_portc_t * portc, int nsamples)
{
  int *outp;
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#ifdef VMIX_USE_FLOAT
  double range = 2147483647.0;
#endif
#include "rec_export.inc"
}

void
vmix_rec_export_32oe (vmix_portc_t * portc, int nsamples)
{
  int *outp;
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap32(x)
#ifdef VMIX_USE_FLOAT
  double range = 2147483647.0;
#endif
#include "rec_export.inc"
}

#ifdef VMIX_USE_FLOAT
void
vmix_rec_export_float (vmix_portc_t * portc, int nsamples)
{
  float *outp;
#undef BUFFER_TYPE
#define BUFFER_TYPE float *
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
  double range = 1.0;
#include "rec_export.inc"
}
#endif

static void
vmix_record_callback (int dev, int parm)
{
  int i, n;
  int do_input = 0;

  adev_t *adev = audio_engines[dev];
  dmap_t *dmap = adev->dmap_in;
  oss_native_word flags;

  vmix_devc_t *devc = vmix_devc;
  vmix_mixer_t *mixer = devc->mixers[parm];
  vmix_engine_t *eng = &mixer->record_engine;

#ifdef VMIX_USE_FLOAT
  fp_env_t fp_buf;
  short *fp_env = fp_buf;
  fp_flags_t fp_flags;
#endif

  UP_STATUS (0x02);
  MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags);
#ifdef VMIX_USE_FLOAT
  {
    /*
     * Align the FP save buffer to 16 byte boundary
     */
    oss_native_word p;
    p = (oss_native_word) fp_buf;

    p = ((p + 15ULL) / 16) * 16;
    fp_env = (short *) p;
  }

  FP_SAVE (fp_env, fp_flags);
#endif

  /*
   * Check if any input applications are active. Skip input processing
   * if it's not needed (to save CPU cycles).
   */

  for (i = 0; i < mixer->num_clientdevs; i++)
    if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_INPUT)
      do_input = 1;

  n = 0;
  while (n++ < dmap->nfrags
	 && (int) (dmap->byte_counter - dmap->user_counter) >=
	 dmap->fragment_size)
    {
      int i, p;
      unsigned char *inbuf;

      if (!do_input)
	{
	  /*
	   * Just skip the recorded data becaus nobody needs it.
	   */
	  dmap->user_counter += dmap->fragment_size;
	  continue;
	}

      for (i = 0; i < eng->channels; i++)
	{
	  memset (eng->chbufs[i], 0, CHBUF_SAMPLES * sizeof (vmix_sample_t));
	}

      p = (int) (dmap->user_counter % dmap->bytes_in_use);
      inbuf = dmap->dmabuf + p;

      eng->converter (eng, inbuf, eng->chbufs, eng->channels,
		      eng->samples_per_frag);

      for (i = 0; i < mixer->num_clientdevs; i++)
	{
	  vmix_portc_t *portc = mixer->client_portc[i];

	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      if (portc->rec_mixing_func == NULL)
		continue;
	      if (portc->rec_choffs + portc->channels >
		  mixer->record_engine.channels)
		portc->rec_choffs = 0;
	      portc->rec_mixing_func (portc,
				      mixer->record_engine.samples_per_frag);
	    }
	}

      dmap->user_counter += dmap->fragment_size;
    }

#ifdef VMIX_USE_FLOAT
  FP_RESTORE (fp_env, fp_flags);
#endif
  MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags);

/*
 * Call oss_audio_inputintr outside FP mode because it may 
 * cause a task switch (under Solaris). Task switch may turn on CR0.TS under
 * x86 which in turn will cause #nm exception.
 */
  for (i = 0; i < mixer->num_clientdevs; i++)
    if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_INPUT)
      {
	vmix_portc_t *portc = mixer->client_portc[i];
	oss_audio_inputintr (portc->audio_dev, 0);
      }
  DOWN_STATUS (0x02);
}

void
finalize_record_engine (vmix_mixer_t * mixer, int fmt, adev_t * adev,
			dmap_p dmap)
{
  int i;

  switch (fmt)
    {
    case AFMT_S16_NE:
      mixer->record_engine.bits = 16;
      mixer->record_engine.converter = import16ne;
      break;

    case AFMT_S16_OE:
      mixer->record_engine.bits = 16;
      mixer->record_engine.converter = import16oe;
      break;

    case AFMT_S32_NE:
      mixer->record_engine.bits = 32;
      mixer->record_engine.converter = import32ne;
      break;

    case AFMT_S32_OE:
      mixer->record_engine.bits = 32;
      mixer->record_engine.converter = import32oe;
      break;

    default:
      cmn_err (CE_CONT, "Unrecognized recording sample format %x\n", fmt);
      return;
    }

  mixer->record_engine.fragsize = dmap->fragment_size;

  mixer->record_engine.samples_per_frag =
    mixer->record_engine.fragsize / mixer->record_engine.channels /
    (mixer->record_engine.bits / 8);

  if (mixer->record_engine.samples_per_frag > CHBUF_SAMPLES)
    {
      cmn_err (CE_WARN, "Too many samples per fragment (%d,%d)\n",
	       mixer->record_engine.samples_per_frag, CHBUF_SAMPLES);
      return;
    }

  for (i = 0; i < mixer->record_engine.channels; i++)
    if (mixer->record_engine.chbufs[i] == NULL)	/* Not allocated yet */
      {
	mixer->record_engine.chbufs[i] =
	  PMALLOC (mixer->master_portc,
		   CHBUF_SAMPLES * sizeof (vmix_sample_t));
	if (mixer->record_engine.chbufs[i] == NULL)
	  {
	    cmn_err (CE_WARN, "Out of memory\n");
	    return;
	  }
      }

  dmap->audio_callback = vmix_record_callback;	/* Enable conversions */
  dmap->callback_parm = mixer->instance_num;
  dmap->dma_mode = PCM_ENABLE_INPUT;

  if (mixer->num_clientdevs > 1)
    adev->redirect_out = mixer->client_portc[0]->audio_dev;
  vmix_record_callback (mixer->inputdev, mixer->instance_num);
}

void
vmix_setup_record_engine (vmix_mixer_t * mixer, adev_t * adev, dmap_t * dmap)
{
  int fmt;
  int old_min;
  int frags = 0x7fff0007;	/* fragment size of 128 bytes */

/*
 * Sample format (and endianess) setup 
 *
 */

  // First make sure a sane format is selected before starting to probe
  fmt = adev->d->adrv_set_format (mixer->inputdev, AFMT_S16_LE);
  fmt = adev->d->adrv_set_format (mixer->inputdev, AFMT_S16_NE);

  // Find out the "best" sample format supported by the device

  if (adev->iformat_mask & AFMT_S16_NE)
    fmt = AFMT_S16_OE;
  if (adev->iformat_mask & AFMT_S16_NE)
    fmt = AFMT_S16_NE;
  if (vmix_multich_enable)
    {
      if (adev->iformat_mask & AFMT_S32_NE)
	fmt = AFMT_S32_OE;
      if (adev->iformat_mask & AFMT_S32_NE)
	fmt = AFMT_S32_NE;
    }

  fmt = adev->d->adrv_set_format (mixer->inputdev, fmt);
  mixer->record_engine.fmt = fmt;

/*
 * Number of channels
 */
  mixer->record_engine.channels = MAX_REC_CHANNELS;
  if (!vmix_multich_enable)
    mixer->record_engine.channels = 2;

  /* Force the device to stereo before trying with (possibly) imultiple channels */
  adev->d->adrv_set_channels (mixer->inputdev, 2);

  mixer->record_engine.channels = mixer->record_engine.channels =
    adev->d->adrv_set_channels (mixer->inputdev,
				mixer->record_engine.channels);

  if (mixer->record_engine.channels > MAX_REC_CHANNELS)
    {
      cmn_err (CE_WARN,
	       "Number of channels (%d) is larger than maximum (%d)\n",
	       mixer->record_engine.channels, MAX_REC_CHANNELS);
      return;
    }

  /*
   * Try to set the same rate than for playback.
   */
  mixer->record_engine.rate =
    oss_audio_set_rate (mixer->inputdev, mixer->play_engine.rate);

  if (mixer->record_engine.rate <= 22050)
    frags = 0x7fff0004;		/* Use smaller fragments */

  audio_engines[mixer->inputdev]->hw_parms.channels =
    mixer->record_engine.channels;
  audio_engines[mixer->inputdev]->hw_parms.rate = mixer->record_engine.rate;
  audio_engines[mixer->inputdev]->dmap_out->data_rate =
    mixer->record_engine.rate * mixer->record_engine.channels *
    mixer->record_engine.bits / 8;
  audio_engines[mixer->inputdev]->dmap_out->frame_size =
    mixer->record_engine.channels * mixer->record_engine.bits / 8;

  old_min = adev->min_fragments;

#if 0
  if ((adev->max_fragments == 0 || adev->max_fragments >= 4)
      && adev->min_block == 0)
    adev->min_fragments = 4;
#endif

  oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_SETFRAGMENT,
		   (ioctl_arg) & frags);
  oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_GETBLKSIZE,
		   (ioctl_arg) & mixer->record_engine.fragsize);

  dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags;

  oss_audio_ioctl (mixer->inputdev, NULL, SNDCTL_DSP_GETBLKSIZE,
		   (ioctl_arg) & mixer->record_engine.fragsize);
  mixer->record_engine.fragsize = dmap->fragment_size;
  adev->min_fragments = old_min;

  if (mixer->record_engine.channels > 2)
    {
      DDB (cmn_err
	   (CE_CONT, "Enabling multi channel rec mode, %d hw channels\n",
	    mixer->record_engine.channels));
    }
  else if (mixer->record_engine.channels != 2)
    {
      cmn_err (CE_WARN,
	       "Master device doesn't support suitable channel configuration\n");

      return;
    }

  finalize_record_engine (mixer, fmt, adev, dmap);
}
